package com.webapp.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.dom4j.tree.AbstractEntity;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.StringUtils;

import com.webapp.entity.BaseEntity;
import com.webapp.vo.Pagination;


public abstract class AbstractDao<T> extends HibernateDaoSupport {

    private Class<T> clazz;

    @SuppressWarnings("unchecked")
    public AbstractDao() {
        this.clazz =  (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public T load(Long id) {
        return (T) getHibernateTemplate().load(clazz, id);
    }

    public T load(Serializable id) {
        return (T) getHibernateTemplate().load(clazz, id);
    }
    
    public List<T> loadAll() {
        return (List<T>) getHibernateTemplate().loadAll(clazz);
    }

    public T get(Long id) {
        return (T) getHibernateTemplate().get(clazz, id);
    }
	
	public T get(Serializable id) {
        return (T) getHibernateTemplate().get(clazz, id);
    }
    
	public Serializable save(Object o) {
		if(o instanceof BaseEntity) {
			BaseEntity entity = (BaseEntity) o;
            entity.setUpdatedAt(new Date());
            entity.setCreatedAt(new Date());
            return getHibernateTemplate().save(o);
		}
		return getHibernateTemplate().save(o);
    }
    
    public Collection<? extends BaseEntity> saveAll(final Collection<? extends BaseEntity> entities) {
        return this.getHibernateTemplate().execute(new HibernateCallback<Collection<?extends BaseEntity>>(){
            public Collection<? extends BaseEntity> doInHibernate(Session session) throws HibernateException, SQLException {
                for(BaseEntity entity: entities) {
                    session.save(entity);
                }
                session.flush();
                return entities;
            }
            
        });
    }
    public Collection<? extends AbstractEntity> saveAllAbstractEntity(final Collection<? extends AbstractEntity> entities) {
    	for(Object o : entities) {
    		this.save(o);
    		this.flush();
    	}
        return entities;
    }

    public void saveOrUpdate(Object o) {
        if (o instanceof BaseEntity) {
            BaseEntity entity = (BaseEntity) o;
            entity.setUpdatedAt(new Date());
            getHibernateTemplate().saveOrUpdate(entity);
        } else {
            getHibernateTemplate().saveOrUpdate(o);
        }
    }

    public void update(Object o) {
        if (o instanceof BaseEntity) {
            BaseEntity entity = (BaseEntity) o;
            entity.setUpdatedAt(new Date());
            getHibernateTemplate().update(entity);
        } else {
            getHibernateTemplate().update(o);
        }
    }

    public Object merge(Object o) {
        return getHibernateTemplate().merge(o);
    }

    public void flush() {
        getHibernateTemplate().flush();
    }

    public void delete(Object o) {
        getHibernateTemplate().delete(o);
    }

    public void deleteAll(List<T> entities) {
        getHibernateTemplate().deleteAll(entities);
    }

    public void deleteAll(Collection<?> entities) {
        getHibernateTemplate().deleteAll(entities);
    }
    @SuppressWarnings("unchecked")
    protected T get(String queryString) {
        List<T> list = getHibernateTemplate().find(from(queryString));
        return list.size() > 0 ? list.get(0) : null;
    }

    @SuppressWarnings("unchecked")
    protected T get(String queryString, Object... values) {
        List<T> list = getHibernateTemplate().find(from(queryString), values);
        return list.size() > 0 ? list.get(0) : null;
    }

    @SuppressWarnings("unchecked")
    protected T get(Object entity) {
        List<T> list = getHibernateTemplate().findByExample(entity);
        return list.size() > 0 ? list.get(0) : null;
    }

    @SuppressWarnings("unchecked")
    protected List<T> find(String queryString) {
        return (List<T>) getHibernateTemplate().find(from(queryString));
    }

    @SuppressWarnings("unchecked")
    protected List<T> find(String queryString, Object... values) {
        return (List<T>) getHibernateTemplate().find(from(queryString), values);
    }
    
    @SuppressWarnings("unchecked")
    protected List<T> find(String queryString, List<Object> values) {
    	if(values == null || values.size() == 0) return this.find(queryString);
    	Object[] paramArray = new Object[values.size()];
    	for (int i=0;i<values.size();i++) {
    		paramArray[i] = values.get(i);
    	}
        return (List<T>) getHibernateTemplate().find(from(queryString), paramArray);
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByNamedQuery(String queryName) {
        return (List<T>) getHibernateTemplate().findByNamedQuery(queryName);
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByNamedQuery(String queryName, Object... values) {
        return (List<T>) getHibernateTemplate().findByNamedQuery(queryName,
            values);
    }

    protected T getByNamedQuery(String queryName) {
        List<T> list = findByNamedQuery(queryName);
        return list.size() > 0 ? list.get(0) : null;
    }

    protected T getByNamedQuery(String queryName, Object... values) {
        List<T> list = findByNamedQuery(queryName, values);
        return list.size() > 0 ? list.get(0) : null;
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(DetachedCriteria criteria) {
        return (List<T>) getHibernateTemplate().findByCriteria(criteria);
    }
    
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(DetachedCriteria criteria,
        int firstResult, int maxResult) {
        return (List<T>) getHibernateTemplate().findByCriteria(criteria,
            firstResult, maxResult);
    }

    protected T getByCriteria(DetachedCriteria criteria) {
        List<T> list = findByCriteria(criteria);
        return list.size() > 0 ? list.get(0) : null;
    }

    protected T getByCriteria(DetachedCriteria criteria, int firstResult,
        int maxResult) {
        List<T> list = findByCriteria(criteria, firstResult, maxResult);
        return list.size() > 0 ? list.get(0) : null;
    }
    
    protected List<T> listByHql(final String hql,final List<Object> params) {
        Object[] paramsArray = null;
        if (params.size()>0) {
            paramsArray = new Object[params.size()];
            for(int i =0 ;i < params.size(); i++) {
                paramsArray[i] = params.get(i);
            }
        }
        return this.listByHql(hql, paramsArray);
    }
    
    @SuppressWarnings("unchecked")
	protected Pagination<T> paginateByCriteria(Pagination<T> p, DetachedCriteria detachedCriteria) {
        Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
        CriteriaImpl impl = (CriteriaImpl) criteria;
        Projection projection = impl.getProjection();
        Long totalCount = ((Long) criteria.setProjection(Projections.rowCount()).uniqueResult());
        p.setTotalCount(totalCount);
        criteria.setProjection(projection);
        if (projection == null) {
            criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
        }

        criteria.setFirstResult(p.getPosStart());
        criteria.setMaxResults(p.getPageSize());
        if (!StringUtils.isEmpty(p.getOrderBy())) {
            if ("desc".equals(p.getDirection())) {
                criteria.addOrder(Order.desc(p.getOrderBy()));
            } else {
                criteria.addOrder(Order.asc(p.getOrderBy()));
            }
        } 
        p.setData(criteria.list());
        
        return p;
    }
    
    @SuppressWarnings("unchecked")
	protected List<T> listByHql(final String hql,final Object...params) {
        return this.getHibernateTemplate().executeFind(new HibernateCallback<List<T>>() {


            public List<T> doInHibernate(Session session) throws HibernateException, SQLException {
                String queryHQL = hql ;
                Query query =  session.createQuery(queryHQL);
                if (params != null && params.length >0) {
                    for(int i =0; i< params.length; i++ ){
                        query.setParameter(i, params[i]);
                    }
                }
                return query.list();
            }
            
        });
       
    }
    protected int bulkUpdate(String queryString) {
        return getHibernateTemplate().bulkUpdate(update(queryString));
    }

    protected int bulkUpdate(String queryString, Object... values) {
        return getHibernateTemplate().bulkUpdate(update(queryString), values);
    }

    protected int bulkDelete(String queryString) {
        return getHibernateTemplate().bulkUpdate(delete(queryString));
    }

    protected int bulkDelete(String queryString, Object... values) {
        return getHibernateTemplate().bulkUpdate(delete(queryString), values);
    }

    protected String delete(String queryString) {
        return "delete from " + clazz.getSimpleName() + " "
            + clazz.getSimpleName().toLowerCase() + " " + queryString;
    }

    private String update(String queryString) {
        return "update " + clazz.getSimpleName() + " "
            + clazz.getSimpleName().toLowerCase() + " " + queryString;
    }

    private String from(String queryString) {
        return "from " + clazz.getSimpleName() + " as "
            + clazz.getSimpleName().toLowerCase() + " " + queryString;
    }

    protected DetachedCriteria criteria() {
    	return DetachedCriteria.forClass(clazz);
    }
    
    public List<T> listByIds(Collection<String> ids) {
    	DetachedCriteria criteria = criteria();
    	criteria.add(Restrictions.in("id", ids));
    	return findByCriteria(criteria);
    }
}
